Python 基础速通(运维向)
这个系列面向有 shell 基础的运维工程师,目标很明确:用 Python 写运维脚本。第 1 篇快速过一遍基础和工具链,从第 2 篇开始全是运维场景。默认环境 macOS / Linux,Python 3.12+,所有示例都可以直接照抄运行。
为什么是 Python
shell 脚本写着写着总会遇到几个坎,这时候就该换 Python 了:
- 脚本超过 100 行:逻辑一复杂,shell 的调试和可读性就崩盘。
- 要处理结构化数据:JSON、YAML、CSV,shell 靠 awk/sed/jq 硬啃,Python 原生支持。
- 要调 API:云厂商、监控平台、内部系统全是 HTTP 接口,Python 发请求是三行的事。
运维场景里 Python 的位置:单机上的小工具、巡检脚本、日志分析、一次性数据处理。多台机器的批量编排交给 Ansible 这类工具(思路见 Ansible 核心概念与架构),Python 负责把单点的事情做漂亮。
不是二选一。实际工作流常常是 Ansible 批量下发,机器上跑的那个脚本本体就是 Python。
安装与版本
系统自带的 python3 别乱动
macOS 装了 Xcode 命令行工具后自带 /usr/bin/python3,Linux 发行版也几乎都有。注意两点:
- 系统 Python 是给系统工具用的,别拿
sudo pip install往里面装包,更别升级或卸载它——Ubuntu 的 apt、一些系统组件都依赖自带 Python,动坏了系统功能会跟着挂。 - 系统自带版本通常偏旧,写自己的脚本建议单独装一份新的。
用 pyenv 管理多版本
多版本管理的思路和 Node 的 nvm 一模一样(不熟的话可以看 nvm 的安装与镜像配置):把多个 Python 版本装进用户目录,一条命令切换,不碰系统 Python。
# macOS
brew install pyenv
# Linux 用官方安装脚本
curl https://pyenv.run | bash
按安装完的提示把初始化语句写进 ~/.zshrc 或 ~/.bashrc,然后:
pyenv install 3.12 # 装一个 3.12 最新小版本
pyenv global 3.12 # 设为默认
python3 -V # 验证
Python 3.12.7
不想装版本管理器也行:macOS 直接 brew install python@3.12,Ubuntu 用 sudo apt install python3,只要版本 ≥ 3.10 本系列的示例都能跑。
虚拟环境
为什么必须隔离:不同项目依赖的包版本会打架。项目 A 要 requests 2.28,项目 B 要 2.31,装在一个环境里必有一个不开心。虚拟环境给每个项目一个独立的包目录,互不干扰。
标准库自带的 venv 就够用,不用装额外工具:
cd ~/ops-scripts
python3 -m venv .venv # 建虚拟环境,目录名约定叫 .venv
source .venv/bin/activate # 激活,提示符前会出现 (.venv)
pip install requests # 装包装进 .venv,不碰系统
deactivate # 退出虚拟环境
激活后 python、pip 都指向 .venv 里的那份,写脚本、跑脚本都在激活状态下进行。
.venv 不进版本库,.gitignore 里加上:
.venv/
__pycache__/
pip 与国内镜像
默认源 PyPI 在国外,直连经常慢到怀疑人生。临时用清华镜像:
pip install requests -i https://pypi.tuna.tsinghua.edu.cn/simple
一劳永逸的做法是写进配置文件 ~/.pip/pip.conf(macOS / Linux 通用):
[global]
index-url = https://pypi.tuna.tsinghua.edu.cn/simple
日常高频命令:
pip install requests # 装包
pip install -U requests # 升级到最新版
pip list # 列出已装的包
pip show requests # 看某个包的版本、依赖详情
pip freeze > requirements.txt # 导出精确版本清单
pip install -r requirements.txt # 在另一台机器上按清单复现
freeze 导出的清单会锁定每一个包的精确版本,交给同事或部署到服务器时用 -r 一键复现,环境一致性靠它保证。
语法速览
只挑运维脚本里天天用的,够写 90% 的巡检和小工具。
变量与四种容器
name = 'web01' # 字符串
port = 22 # 数字
servers = ['web01', 'web02', 'db01'] # list:有序、可改
server = {'name': 'web01', 'ip': '10.0.0.11'} # dict:键值对
zones = {'cn-north', 'cn-south'} # set:自动去重
addr = ('10.0.0.11', 22) # tuple:不可改
list 和 dict 用得最多;set 常用来去重;tuple 一般是函数返回多个值时顺手用。
切片
servers = ['web01', 'web02', 'db01', 'db02', 'cache01']
servers[0] # 'web01',下标从 0 开始
servers[-1] # 'cache01',负数从尾部数
servers[1:3] # ['web02', 'db01'],含头不含尾
servers[:2] # ['web01', 'web02']
字典取值:get 与默认值
直接 d['key'] 遇到键不存在会抛 KeyError,运维数据经常缺字段,用 get 更稳:
host = {'ip': '10.0.0.11'}
host.get('ip') # '10.0.0.11'
host.get('port') # None,不报错
host.get('port', 22) # 22,键不存在时给默认值
判断与循环
usage = 87
if usage >= 90:
print('磁盘告急')
elif usage >= 80:
print('磁盘偏高')
else:
print('正常')
for server in ['web01', 'web02']:
print('巡检', server)
retry = 0
while retry < 3:
retry += 1
print(f'第 {retry} 次重试')
列表推导式
一行完成「过滤 + 变形」,处理文件名、IP 列表时极顺手:
files = ['a.log', 'b.txt', 'c.log']
logs = [f for f in files if f.endswith('.log')]
print(logs) # ['a.log', 'c.log']
函数与默认参数
def check_disk(path='/', threshold=80):
"""使用率超过 threshold 返回 True"""
usage = get_usage(path) # 假设有这个函数
return usage > threshold
默认参数让调用方少写字:check_disk() 等价于 check_disk('/', 80)。
try/except 抓异常
运维脚本面对的环境不干净:文件可能不存在、权限可能不够。把可能出错的代码包进 try:
try:
with open('/etc/shadow', encoding='utf-8') as f:
f.read()
except PermissionError:
print('权限不够,需要 sudo')
except FileNotFoundError:
print('文件不存在')
抓具体异常类型,别用裸 except: 把所有错误(包括 Ctrl+C)都吞掉。
with open 读写文件
# 读:with 块结束自动关文件,不怕忘
with open('/etc/hosts', encoding='utf-8') as f:
content = f.read()
# 写:'w' 覆盖写,'a' 追加写
with open('/tmp/result.txt', 'w', encoding='utf-8') as f:
f.write('巡检完成\n')
读大文件别 read() 一次性吞,逐行迭代:for line in f:。
f-string 格式化
字符串前加 f,花括号里直接放变量和表达式,Python 3.6+ 的标配写法:
name = 'web01'
usage = 87.456
print(f'主机 {name} 磁盘使用率 {usage:.1f}%')
主机 web01 磁盘使用率 87.5%
:.1f 是格式说明,表示保留一位小数。
一个能跑的运维小脚本
把上面的语法串起来:读 /etc/hosts,统计里面一共映射了多少个主机名(跳过空行和注释)。
存成 count_hosts.py:
#!/usr/bin/env python3
"""统计 /etc/hosts 中有效主机名条目的数量"""
def count_hosts(path='/etc/hosts'):
total = 0
with open(path, encoding='utf-8') as f:
for line in f:
line = line.split('#', 1)[0].strip() # 去掉行尾注释
if not line:
continue
total += len(line.split()) - 1 # 首列是 IP,其余都是主机名
return total
if __name__ == '__main__':
print(f'/etc/hosts 里共有 {count_hosts()} 个主机名条目')
运行:
python3 count_hosts.py
/etc/hosts 里共有 5 个主机名条目
两个约定解释一下:
- 第一行 shebang(
#!/usr/bin/env python3)让脚本可以chmod +x后直接./count_hosts.py执行。 if __name__ == '__main__'块里的代码只在直接运行脚本时执行,文件被别人 import 时不触发,写入口脚本都这么收尾。
常见坑
python与python3不是一个东西:很多系统上python不存在,或指向古董 Python 2。命令行里固定用python3/pip3;进了虚拟环境后python才安全地指向 3.x。- 全局 pip install 污染系统:新版 pip 会直接拒绝并报
externally-managed-environment(PEP 668),这是好事,逼你回虚拟环境。别加--break-system-packages硬来。 - 缩进错误:Python 用缩进划分代码块,混用 Tab 和空格会报
TabError。编辑器设置成「Tab 展开为 4 个空格」,团队统一就没事了。
小结
- 脚本超 100 行、要处理 JSON/YAML、要调 API——这三种情况放下 shell,换 Python。
- 系统 Python 别动,自己的版本用 pyenv 或 brew/apt 装,
python3 -V验证。 - 每个项目一个
.venv虚拟环境,pip 配清华镜像,freeze锁版本保证可复现。 - 语法记住四种容器、
get取字典、推导式、try/except、with open、f-string,就够开工了。
基础过关后,下一篇进入正题:不用装任何第三方库,标准库里的 os、subprocess、shutil、logging、argparse 等模块就能把日常运维脚本写出来。